Imports Aricie.DNN.Caching
Imports Aricie.Collections
Imports Aricie.DNN.Caching.Analysis.Graphs
Imports QuickGraph.Algorithms.Condensation
Imports QuickGraph.Predicates

Namespace Aricie.DNN.Caching.Analysis
    Public Class TargetStrategyPool

        Friend CloudSettings As CloudSettings
        Private _Statistics As CacheStatistics
        Private _TargetJobsByKey As New DualDictionary(Of String, TargetJob)(True, EqualityComparer(Of String).Default)
        'Private _TagsByKeys As New DualDictionary(Of String, String)
        Private _TargetGraph As New DistributedCachingGraph


        Public Sub New()

        End Sub

        Public Sub New(ByVal stats As CacheStatistics)
            Me._Statistics = stats
            Me._TargetGraph = stats.StatisticsGraph
        End Sub


        Public Sub New(ByVal CloudSettings As CloudSettings)
            Me.CloudSettings = CloudSettings
            Dim jobs As Dictionary(Of String, CacheJob) = CloudSettings.GetAllJobs(True)
            For Each keyPair As KeyValuePair(Of String, CacheJob) In jobs
                Dim tJob As New TargetJob(keyPair.Value.StrategyType, _
                                          keyPair.Value.Strategy.DistributionType, keyPair.Value.Strategy.PutTags)
                Me.SetTarget(keyPair.Key, tJob)
            Next
        End Sub


        Public Property TargetGraph() As DistributedCachingGraph
            Get
                Return _TargetGraph
            End Get
            Set(ByVal value As DistributedCachingGraph)
                _TargetGraph = value
            End Set
        End Property

        Public ReadOnly Property TargetJobsByKey() As DualDictionary(Of String, TargetJob)
            Get
                Return _TargetJobsByKey
            End Get
        End Property



        Public Function TryGetTargetByKey(ByVal key As String, ByRef objTarget As TargetJob) As Boolean
            Return Me.TargetJobsByKey.TryGetValue(key, objTarget)
        End Function

        Public Function TryGetKeysByTarget(ByVal target As TargetJob, ByRef keys As IList(Of String)) As Boolean
            Return TargetJobsByKey.Reverse.TryGetValue(target, keys)
        End Function







        Public Sub SetTarget(ByVal key As String, ByVal target As TargetJob)
            _TargetJobsByKey(key) = target
        End Sub



        Public Function Compute(ByVal settings As AutoConfigSettings, ByVal existing As TargetStrategyPool) As CacheAnalysis
            ComputeMainGroups(settings)
            If Not settings.NoClusters Then
                Me.ComputeClusters(settings)
            End If
            Dim toReturn As New CacheAnalysis()
            toReturn.Statistics = Me._Statistics
            If settings.IndividualStrategies Then
                ComputeIndividualStats(settings)
            End If
            toReturn.AnalysisGraph = Me.TargetGraph
            toReturn.GroupStrategies = MakeGroups(settings, existing)

            Return toReturn
        End Function


        Private Sub ComputeMainGroups(ByVal settings As AutoConfigSettings)

            'Dim groups As New Dictionary(Of KeyValuePair(Of TargetJobNature, CacheDistributionType), CacheGroupInfo)

            For Each keyStat As KeyValuePair(Of String, CacheKeyStatistics) In Me._Statistics.StatsByKey
                If keyStat.Value.Diagnostic.SortedDistributionTypes.Count = 0 Then
                    Me.SetTarget(keyStat.Key, TargetJob.InvalidJob)
                ElseIf keyStat.Value.Diagnostic.SortedDistributionTypes.Count = 1 Then
                    Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, keyStat.Value.Diagnostic.SortedDistributionTypes(0).DistributionType))
                Else
                    If keyStat.Value.Diagnostic.SortedDistributionTypes(0).DistributionType = CacheDistributionType.NotDistributed Then
                        If keyStat.Value.Diagnostic.SortedDistributionTypes(1).RatioToBest > settings.MinLossForNoDistribution Then
                            Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, keyStat.Value.Diagnostic.SortedDistributionTypes(0).DistributionType))
                        Else
                            'Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.Undefined))
                            Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.Undefined))
                        End If
                    Else
                        If keyStat.Value.Diagnostic.SortedDistributionTypes(1).RatioToBest > 10000 / settings.MinGainForDistribution _
                            OrElse keyStat.Value.Diagnostic.SortedDistributionTypes(1).DistributionType <> CacheDistributionType.NotDistributed Then
                            Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, keyStat.Value.Diagnostic.SortedDistributionTypes(0).DistributionType))
                        Else

                            Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.Undefined))
                            'Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.Undefined))

                        End If
                    End If

                End If
            Next
        End Sub




        Private Sub ComputeClusters(ByVal settings As AutoConfigSettings)
            Me.PerformCondensation(settings)
            For Each objGroup As DistributedCachingNode In Me._TargetGraph.Vertices
                If objGroup.NodeType = TargetNodeType.Group Then
                    Dim objGroupName As String = objGroup.KeysGroup.Name
                    Dim undefinedKeys As IList(Of String) = Nothing
                    If Me.TryGetKeysByTarget(New TargetJob(StrategyType.Group, CacheDistributionType.Undefined, objGroupName), undefinedKeys) Then
                        For Each undefinedKey As String In New List(Of String)(undefinedKeys)
                            Dim keyStat As CacheKeyStatistics = Nothing
                            If Me._Statistics.StatsByKey.TryGetValue(undefinedKey, keyStat) Then
                                If keyStat.Diagnostic.SortedDistributionTypes(0).DistributionType = CacheDistributionType.NotDistributed Then
                                    If keyStat.Diagnostic.SortedDistributionTypes(1).RatioToBest < settings.MaxLossForBatch Then
                                        Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, keyStat.Diagnostic.SortedDistributionTypes(1).DistributionType, objGroupName))
                                    Else
                                        Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.NotDistributed, objGroupName))
                                    End If
                                Else
                                    Me.SetTarget(keyStat.Key, New TargetJob(StrategyType.Group, keyStat.Diagnostic.SortedDistributionTypes(0).DistributionType))
                                End If
                            End If
                        Next
                    End If
                End If
            Next

        End Sub

        Private Sub PerformCondensation(ByVal settings As AutoConfigSettings)
            Dim graphFilter As New ClusteringGraphFilter(settings, _TargetGraph)
            Dim filteredGraph As New FilteredBidirectionalGraph(Of DistributedCachingNode, DistributedCachingEdge, DistributedCachingGraph)(Me.TargetGraph, _
                                                                                                                                            AddressOf graphFilter.VertexPredicate, AddressOf graphFilter.EdgePredicate)
            Dim condensator As New QuickGraph.Algorithms.Condensation.CondensationGraphAlgorithm(Of DistributedCachingNode, DistributedCachingEdge, DistributedCachingGraph)(filteredGraph)
            condensator.StronglyConnected = False
            condensator.Compute()
            Dim timeStamp As String = DateTime.Now.ToShortDateString & "-Group-"
            Dim i As Integer = 1
            Dim groups As New Dictionary(Of DistributedCachingGraph, DistributedCachingNode)
            'add new nodes
            For Each objGroup As DistributedCachingGraph In condensator.CondensedGraph.Vertices
                If objGroup.VertexCount >= settings.MinGroupSize Then
                    Dim groupName As String = timeStamp & i.ToString
                    i += 1
                    Dim groupNode As New DistributedCachingNode(groupName, objGroup)
                    groups.Add(objGroup, groupNode)
                    Me.TargetGraph.AddVertex(groupNode)
                    For Each objNode As DistributedCachingNode In objGroup.Vertices

                        objNode.Grouped = True
                        'Dim subNode As DistributedCachingNode = objNode
                        If objNode.NodeType = TargetNodeType.KeyStat Then
                            Dim objJob As TargetJob = Nothing
                            If Me.TryGetTargetByKey(objNode.KeyStat.Key, objJob) Then
                                Me.SetTarget(objNode.KeyStat.Key, New TargetJob(objJob, groupName))
                            Else
                                Me.SetTarget(objNode.KeyStat.Key, New TargetJob(StrategyType.Group, CacheDistributionType.Undefined, groupName))
                            End If

                        End If

                        Dim objCondensedEdge As DistributedCachingEdge = Nothing
                        'For Each objCondensedEdge As CondensedEdge(Of DistributedCachingNode, DistributedCachingEdge, DistributedCachingGraph) In condensator.CondensedGraph.Edges
                        For Each objInEdge As DistributedCachingEdge In Me.TargetGraph.InEdges(objNode)

                            'Dim startGroup As DistributedCachingGraph = objCondensedEdge.Source
                            'Dim startGroupNode As DistributedCachingNode = Nothing
                            'Dim endGroup As DistributedCachingGraph = objCondensedEdge.Target
                            'Dim endGroupNode As DistributedCachingNode = Nothing

                            If Not Me.TargetGraph.TryGetEdge(objInEdge.Source, groupNode, objCondensedEdge) Then
                                objCondensedEdge = New DistributedCachingEdge(objInEdge.Source, groupNode, objInEdge.Tag)
                                Me.TargetGraph.AddEdge(objCondensedEdge)
                            Else
                                objCondensedEdge.Tag = New CacheSequenceTag(objCondensedEdge.Tag.Count + objInEdge.Tag.Count, _
                                                                   Convert.ToInt32((objCondensedEdge.Tag.Count * objCondensedEdge.Tag.Frequency + objInEdge.Tag.Count * objInEdge.Tag.Frequency) / (objCondensedEdge.Tag.Count + objInEdge.Tag.Count)), objInEdge.Tag.IsMain)
                            End If

                           
                        Next

                        For Each objOutEdge As DistributedCachingEdge In Me.TargetGraph.OutEdges(objNode)

                            'Dim startGroup As DistributedCachingGraph = objCondensedEdge.Source
                            'Dim startGroupNode As DistributedCachingNode = Nothing
                            'Dim endGroup As DistributedCachingGraph = objCondensedEdge.Target
                            'Dim endGroupNode As DistributedCachingNode = Nothing

                            If Not Me.TargetGraph.TryGetEdge(groupNode, objOutEdge.Target, objCondensedEdge) Then
                                objCondensedEdge = New DistributedCachingEdge(groupNode, objOutEdge.Target, objOutEdge.Tag)
                                Me.TargetGraph.AddEdge(objCondensedEdge)
                            Else
                                objCondensedEdge.Tag = New CacheSequenceTag(objCondensedEdge.Tag.Count + objOutEdge.Tag.Count, _
                                            Convert.ToInt32((objCondensedEdge.Tag.Count * objCondensedEdge.Tag.Frequency + objOutEdge.Tag.Count * objOutEdge.Tag.Frequency) / (objCondensedEdge.Tag.Count + objOutEdge.Tag.Count)), objOutEdge.Tag.IsMain)
                            End If


                        Next

                        Me.TargetGraph.RemoveVertex(objNode)

                    Next
                End If
            Next
            ''Add new edges
            'For Each objCondensedEdge As CondensedEdge(Of DistributedCachingNode, DistributedCachingEdge, DistributedCachingGraph) In condensator.CondensedGraph.Edges
            '    Dim startGroup As DistributedCachingGraph = objCondensedEdge.Source
            '    Dim startGroupNode As DistributedCachingNode = Nothing
            '    Dim endGroup As DistributedCachingGraph = objCondensedEdge.Target
            '    Dim endGroupNode As DistributedCachingNode = Nothing
            '    Dim objEdge As DistributedCachingEdge = Nothing
            '    If groups.TryGetValue(startGroup, startGroupNode) Then

            '        If groups.TryGetValue(endGroup, endGroupNode) Then
            '            Dim count As Integer = 0
            '            Dim cumFreq As Integer
            '            For Each statEdge As DistributedCachingEdge In objCondensedEdge.Edges
            '                count += statEdge.Tag.Count
            '                cumFreq += statEdge.Tag.Frequency * statEdge.Tag.Count
            '            Next
            '            cumFreq = Convert.ToInt32(cumFreq / count)
            '            objEdge = New DistributedCachingEdge(startGroupNode, endGroupNode, New CacheSequenceTag(count, cumFreq, False))
            '            Me.TargetGraph.AddEdge(objEdge)
            '        Else
            '            For Each statEdge As DistributedCachingEdge In objCondensedEdge.Edges
            '                If Not Me.TargetGraph.TryGetEdge(startGroupNode, statEdge.Target, objEdge) Then
            '                    objEdge = New DistributedCachingEdge(startGroupNode, statEdge.Target, statEdge.Tag)
            '                    Me.TargetGraph.AddEdge(objEdge)
            '                Else
            '                    objEdge.Tag = New CacheSequenceTag(objEdge.Tag.Count + statEdge.Tag.Count, _
            '                                                       Convert.ToInt32((objEdge.Tag.Count * objEdge.Tag.Frequency + statEdge.Tag.Count * statEdge.Tag.Frequency) / (objEdge.Tag.Count + statEdge.Tag.Count)), statEdge.Tag.IsMain)
            '                End If
            '            Next
            '        End If
            '    ElseIf groups.TryGetValue(endGroup, endGroupNode) Then
            '        For Each statEdge As DistributedCachingEdge In objCondensedEdge.Edges
            '            If Not Me.TargetGraph.TryGetEdge(statEdge.Source, endGroupNode, objEdge) Then
            '                objEdge = New DistributedCachingEdge(statEdge.Source, endGroupNode, statEdge.Tag)
            '                Me.TargetGraph.AddEdge(objEdge)
            '            Else
            '                objEdge.Tag = New CacheSequenceTag(objEdge.Tag.Count + statEdge.Tag.Count, _
            '                                                   Convert.ToInt32((objEdge.Tag.Count * objEdge.Tag.Frequency + statEdge.Tag.Count * statEdge.Tag.Frequency) / (objEdge.Tag.Count + statEdge.Tag.Count)), statEdge.Tag.IsMain)
            '            End If
            '        Next
            '    End If
            'Next
        End Sub




        Private Sub ComputeIndividualStats(ByVal settings As AutoConfigSettings)

        End Sub

        'todo: fix that function
        Private Function MakeGroups(ByVal settings As AutoConfigSettings, ByVal existingPool As TargetStrategyPool) As List(Of CacheGroupInfo)
            Dim toReturn As New List(Of CacheGroupInfo)

            Dim tempDico As New Dictionary(Of TargetJob, CacheGroupInfo)()
            Dim existing As IList(Of String) = Nothing

            Dim mappingFromExisting As New Dictionary(Of CacheGroupInfo, CacheGroupInfo)
            For Each jobPair As KeyValuePair(Of TargetJob, IList(Of String)) In Me.TargetJobsByKey.Reverse
                If jobPair.Key.StrategyType = StrategyType.Group _
                   AndAlso (jobPair.Key.Tags <> "" OrElse jobPair.Key.DistributionType <> CacheDistributionType.Undefined) Then
                    'adding new group
                    Dim newGroup As CacheGroupInfo = Nothing
                    Dim existingCacheJob As CacheJob = Nothing
                    Dim existingGroup As CacheGroupInfo
                    If existingPool.TryGetKeysByTarget(jobPair.Key, existing) Then
                        existingCacheJob = existingPool.CloudSettings.GetKeyJob(jobPair.Value(0))
                        If existingCacheJob.StrategyType = StrategyType.Group Then
                            existingGroup = DirectCast(existingCacheJob.Strategy, CacheGroupInfo)
                            If Not mappingFromExisting.TryGetValue(existingGroup, newGroup) Then
                                newGroup = New CacheGroupInfo(existingGroup.Name, existingCacheJob.Strategy)
                                newGroup.Keys.AddRange(existingGroup.Keys)
                                mappingFromExisting(existingGroup) = newGroup
                            End If
                        End If
                    Else

                        Dim name As String = jobPair.Key.DistributionType.ToString
                        If jobPair.Key.Tags <> "" Then
                            name &= jobPair.Key.Tags
                        End If
                        newGroup = New CacheGroupInfo(name, existingPool.CloudSettings.DefaultKeyConfig)
                        newGroup.LogOperations = False
                        newGroup.DistributionType = jobPair.Key.DistributionType
                        If jobPair.Key.Tags <> "" Then
                            newGroup.PutTags = jobPair.Key.Tags
                            If jobPair.Key.DistributionType <> CacheDistributionType.NotDistributed Then
                                newGroup.BatchMode = BatchMode.AsyncGetByTag
                                newGroup.GetTags = jobPair.Key.Tags
                            End If
                        End If
                        newGroup.Keys.AddRange(jobPair.Value)
                    End If
                    toReturn.Add(newGroup)
                    'cleaning obsolete keys
                    Dim newOldGroup As CacheGroupInfo = Nothing
                    For Each newKey As String In jobPair.Value
                        existingCacheJob = existingPool.CloudSettings.GetKeyJob(newKey)
                        If existingCacheJob.StrategyType = StrategyType.Group Then
                            existingGroup = DirectCast(existingCacheJob.Strategy, CacheGroupInfo)

                            If Not mappingFromExisting.TryGetValue(existingGroup, newOldGroup) Then
                                newOldGroup = New CacheGroupInfo(existingGroup.Name, existingCacheJob.Strategy)
                                newOldGroup.Keys.AddRange(existingGroup.Keys)
                                toReturn.Add(newOldGroup)
                                mappingFromExisting(existingGroup) = newOldGroup
                            End If
                            If Not newOldGroup Is newGroup Then
                                newOldGroup.Keys.Remove(newKey)
                            End If
                        End If
                        If Not newGroup.Keys.Contains(newKey) Then
                            newGroup.Keys.Add(newKey)
                        End If
                    Next

                End If
            Next


            Return toReturn
        End Function









    End Class
End Namespace